package buffer

import "os"

type Buffer interface {
	Size() int                      // number of actual lines
	Get(line int) string            // the content of that line
	DeleteRune(line, col int)       // delete rune on that line  
	Split(line, col int)            // split line at indicated col           
	Join(line int)                  // join this line and the next one 
	Insert(rune int, line, col int) // insert rune 
	AppendLine(text string)         // append that line to the buffer
	DeleteLine(line int)            // delete that line
	//
	DiscardNotes()                 // discard all notes in the buffer
	AddNote(line int, note string) // add a note for that line
	GetNotes(line int) []string    // the notes for that line
}

func WriteBuffer(b Buffer, fileName string) error {
	newline := []byte{'\n'}
	of, err := os.Create(fileName)
	if err == nil {
		defer of.Close()
		size := b.Size()
		for i := 0; i < size; i += 1 {
			// HORRID. Will fix. TODO
			of.WriteString(b.Get(i))
			of.Write(newline)
		}
	}
	return err
}

type note struct {
	line     int
	notelets []string
}

type buffer struct {
	content []string
	notes   []note
}

func NewBuffer() *buffer {
	return &buffer{
		content: []string{}, notes: []note{},
	}
}

func (b *buffer) renumberJoin(line int) {
	l := line + 1
	var (
		nfirst *note = nil
		nnext  *note = nil
	)
	for i, n := range b.notes {
		if n.line > l {
			b.notes[i].line -= 1
		} else if n.line == l {
			nnext = &b.notes[i]
		} else if n.line == line {
			nfirst = &b.notes[i]
		}
	}
	if nfirst == nil {
		if nnext == nil {
		} else {
			nnext.line -= 1
		}
	} else {
		if nnext == nil {
		} else {
			nfirst.notelets = append(nfirst.notelets, nnext.notelets...)
		}
	}
}

func (b *buffer) renumberDelete(line int) {
	deletes := map[int]bool{}
	for i, n := range b.notes {
		if n.line > line {
			b.notes[i].line -= 1
		} else if n.line == line {
			deletes[i] = true
		}
	}
	// remove all the notes with indicies in `deletes`.
	writer := 0
	for reader := 0; reader < len(b.notes); reader += 1 {
		if !deletes[reader] {
			b.notes[writer] = b.notes[reader]
			writer += 1
		}
	}
	b.notes = b.notes[:writer]
}

func (b *buffer) renumber(line int, atLeftEnd, atRightEnd bool) {
	extras := []note{}
	for i, n := range b.notes {
		if n.line > line {
			b.notes[i].line += 1
		} else if n.line == line {
			// a note on this very line: move, stay, or replicate?
			if !atRightEnd {
				b.notes[i].line += 1
			}
			if !atLeftEnd {
				extras = append(extras, n)
			}
		}
	}
	b.notes = append(b.notes, extras...)
}

func (b *buffer) DiscardNotes() {
	b.notes = []note{}
}

func (b *buffer) AddNote(line int, text string) {
	for i, n := range b.notes {
		if n.line == line {
			b.notes[i].notelets = append(b.notes[i].notelets, text)
			return
		}
	}
	b.notes = append(b.notes, note{line, []string{text}})
}

func (b *buffer) GetNotes(line int) []string {
	for _, n := range b.notes {
		if n.line == line {
			return n.notelets
		}
	}
	return nil
}

func (b *buffer) Size() int {
	return len(b.content)
}

func (b *buffer) Get(line int) string {
	return b.content[line]
}

func (b *buffer) DeleteRune(line, col int) {
	if line < len(b.content) {
		current := b.content[line]
		if 0 < col && col < len(current) {
			newLine := current[:col-1] + current[col:]
			b.content[line] = newLine
		}
	}
}

func (b *buffer) Split(line, col int) {
	if line <= len(b.content) {
		current := b.content[line]
		atRightEnd := col >= len(current)
		for len(current) <= col {
			current += " "
		} // TODO make better
		b.content = append(b.content, "")
		copy(b.content[line+2:], b.content[line+1:])
		b.content[line] = current[:col]
		b.content[line+1] = current[col:]
		b.renumber(line, col == 0, atRightEnd)
	}
}

func (b *buffer) AppendLine(text string) {
	b.content = append(b.content, text)
}

func (b *buffer) DeleteLine(line int) {
	if line < len(b.content) {
		if line < len(b.content)-1 {
			copy(b.content[line:], b.content[line+1:])
		}
		b.content = b.content[:len(b.content)-1]
	}
	b.renumberDelete(line)
}

func (b *buffer) Join(line int) {
	if line < len(b.content)-1 {
		// TODO still may need some space?
		b.content[line] += b.content[line+1]
		copy(b.content[line+1:], b.content[line+2:])
		b.content = b.content[:len(b.content)-1]
		b.renumberJoin(line)
	}
}

func (b *buffer) Insert(rune int, line, col int) {
	if ' ' <= rune && rune <= 126 {
		for len(b.content) <= line {
			b.content = append(b.content, "")
		}
		for len(b.content[line]) <= col {
			b.content[line] += " "
		}
		ch := string([]byte{byte(rune)})
		current := b.content[line]
		newLine := current[:col] + ch + current[col:]
		b.content[line] = newLine
		// panel.Log("Insert: line now", newLine)
	}
}
